/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.wsinfra.deployment.impl;

import java.util.Hashtable;
import java.util.LinkedList;
import java.util.ListIterator;

import javax.xml.soap.SOAPMessage;

import cgl.narada.wsinfra.WsMessageFlow;
import cgl.narada.wsinfra.deployment.Filter;
import cgl.narada.wsinfra.deployment.FilterPipeline;
import cgl.narada.wsinfra.deployment.NetworkSubstrate;
import cgl.narada.wsinfra.deployment.SOAPContext;
import cgl.narada.wsinfra.deployment.SOAPContextFactory;
import cgl.narada.wsinfra.deployment.ServiceMessageListener;
import cgl.narada.wsinfra.exception.DeploymentException;
import cgl.narada.wsinfra.exception.IncorrectExchangeException;
import cgl.narada.wsinfra.exception.MessageFlowException;
import cgl.narada.wsinfra.exception.ProcessingException;
import cgl.narada.wsinfra.exception.UnknownExchangeException;

/** This class implements all the capabilities outlined within the 
    FilterPipeline interface.

    @author Shrideep Pallickara
    $Date: 2005/07/29 22:41:29 $
    $Revision: 1.8 $
*/

public class FilterPipelineImpl implements FilterPipeline {

  private ServiceMessageListener serviceMessageListener;
  private NetworkSubstrate networkSubstrate;
  private String pipelineIdentifier;
  private LinkedList filterLinkedList;
  private Hashtable filterHashtable;
  private SOAPContextFactory soapContextFactory;

  private String moduleName = "FilterPipelineImpl: ";

  public FilterPipelineImpl() {
    filterLinkedList = new LinkedList();
    filterHashtable = new Hashtable();
    soapContextFactory = SOAPContextFactory.getInstance();
  }

  /** Register the service message listener for this filter pipeline. */
  public void 
  registerServiceMessageListener(ServiceMessageListener serviceMessageListener)
    throws DeploymentException {
    if (serviceMessageListener == null) {
      throwDeploymentException("Specified ServiceMessageListener is NULL.");
    }
    this.serviceMessageListener = serviceMessageListener;
  }
  
  
  /** Register a networking substrate for this filter pipeline. */
  public void registerNetworkSubstrate(NetworkSubstrate networkSubstrate) 
    throws DeploymentException {
    if (networkSubstrate == null) {
      throwDeploymentException("Specified NetworkSubstrate is NULL.");
    }
    this.networkSubstrate = networkSubstrate;
  }
  
  
  /** Set the identifier of the filter pipeline.*/
  public void setIdentifier(String pipelineIdentifier) 
    throws DeploymentException {
    if (pipelineIdentifier == null) {
      throwDeploymentException("Specified pipelineIdentifier is NULL.");
    }
    this.pipelineIdentifier = pipelineIdentifier;
  }
  
  
  /** Retrieve the identifier of the filter pipeline. */
  public String getIdentifier() {
    return pipelineIdentifier;
  }
  
  
  /** Checks to see if a given filter is part of this pipeline. */
  public boolean isPartOfPipeline(Filter filter) {
    if (filter == null) {
      return false;
    }
    
    String filterIdentifier = filter.getIdentifier();
    if (filterIdentifier == null) {
      return false;
    }
    
    if (!filterHashtable.containsKey(filterIdentifier)) {
      return false;
    }

    return true;
  }
  
  
  /** Add a filter to this filter pipeline. */
  public void addFilter(Filter filter) throws DeploymentException {
    boolean newAddition = true;
    checkFilterForProblems(filter, newAddition);
    
    String identifier = filter.getIdentifier();
    filterHashtable.put(identifier, filter);
    filterLinkedList.add(filter);

    filter.setFilterPipeline(this);
  }

  
  /** Add a filter to this filter pipeline at a specific position. */
  public void addFilterAt(Filter filter, int position) 
    throws DeploymentException {
    boolean newAddition = true;
    checkFilterForProblems(filter, newAddition);
    checkPositionForProblems(position);

    String identifier = filter.getIdentifier();
    filterHashtable.put(identifier, filter);
    filterLinkedList.add(position, filter);

    filter.setFilterPipeline(this);
  }


  /** Remove a filter from this filter-pipeline. */
  public void removeFilter(Filter filter) throws DeploymentException {
    boolean newAddition = false; /** This is the filter that will occupy the
				    new position. */
    checkFilterForProblems(filter, newAddition);
    
    filterLinkedList.remove(filter);
    filterHashtable.remove(filter.getIdentifier());

  }
  
  
  /** Remove a filter at the specified location. */
  public void removeFilterAt(int position) throws DeploymentException {
    checkPositionForProblems(position);
    
    Filter filter = (Filter) filterLinkedList.remove(position);
    filterHashtable.remove(filter.getIdentifier());    
  }


  /** This method moves a filter within a filter chain to the specified 
      position. The positions of all the other filters within the filter 
      pipeline are adjusted accordingly. For e.g.
      F1 --- F2 --- F3 --- F4 --- F5, if you move F3 to the 5th position
      the filter chain would now look like F1 --- F2 --- F4 --- F5 --- F3. 
      This method throws an exception if the filter does not belong to this
      filter pipeline OR if the position is not a valid one. The position
      number CANNOT exceed the number of filters within the filter chain. 
      Also, filter numbering begins at 0. */
  public void moveFilter(Filter filter, int position) 
    throws DeploymentException {
    boolean newAddition = false;
    checkFilterForProblems(filter, newAddition);
    checkPositionForProblems(position);
    
    filterLinkedList.remove(filter);
    filterLinkedList.add(position, filter);
  }
  
  
  /** This method replaces the existing filter at a given position with
      the specified one. */
  public void replaceFilter(Filter filter, int position) 
    throws DeploymentException  {
    boolean newAddition = true; /** This is the filter that will occupy the
				    new position. */
    checkFilterForProblems(filter, newAddition);
    checkPositionForProblems(position);
    
    Filter removedFilter = (Filter) filterLinkedList.set(position, filter);
    String removedIdentifier = removedFilter.getIdentifier();
    
    filterHashtable.remove(removedIdentifier);
    filterHashtable.put(filter.getIdentifier(), filter);
  }
  
  
  
  
  /** Process a message received over the network. */
  public void processMessageFromNetwork(SOAPMessage soapMessage) {
    try {
      int position = filterLinkedList.size() - 1;
      SOAPContext soapContext = soapContextFactory.newSOAPContext(soapMessage);
      propagateMessageTowardsApplication(soapContext, position);
    } catch (DeploymentException de) {
      System.out.println(moduleName + de.toString() + "\n");
      de.printStackTrace();
    }
  }
  

  /** Injects a message based on the filter's position within the filter 
      pipeline towards the application. */
  public void injectMessageTowardsApplication(SOAPMessage soapMessage, 
					      Filter filter) 
    throws DeploymentException {
    boolean newAddition = false; 
    checkFilterForProblems(filter, newAddition);
    
    /** We will start processing after this position. If the filter is 
	injecting a message, we assume that this filter does not need to 
	process this same message again. */
    int position = filterLinkedList.indexOf(filter);
    position--;
    
    SOAPContext soapContext = soapContextFactory.newSOAPContext(soapMessage);
    propagateMessageTowardsApplication(soapContext, position);    
  }


  /** Injects a message at a specific location within the filter pipeline 
      towards the application. */
  public void 
  injectMessageTowardsApplication(SOAPMessage soapMessage, int position) 
    throws DeploymentException {
    
    checkPositionForProblems(position);
    SOAPContext soapContext = soapContextFactory.newSOAPContext(soapMessage);
    propagateMessageTowardsApplication(soapContext, position);    
  }
  

  /** This method propagates a message towards the network, starting at the
      <i>position</i> that has been specified, with decreasing values of the 
      <i>position</i> moving the message closer to the application. */
  private void 
  propagateMessageTowardsApplication(SOAPContext soapContext, int position) {
    System.out.println(moduleName + "Propagating message towards application"
		       + ", position = " + position);
    
    executeInputFiltersStartingAt(soapContext, position);
    System.out.println("\n\n\n\n");

    if (serviceMessageListener != null) {
      serviceMessageListener.onMessage(soapContext.getSOAPMessage());
    }
  }


  /** Excutes input filters within a filter pipeline starting at the 
      specified position. */
  private void 
  executeInputFiltersStartingAt(SOAPContext soapContext, int position) {
    for (int i= position; i >= 0; i--) {
      Filter filter = (Filter) filterLinkedList.get(i);
      
      if (!filter.isInputFilter()) {
	continue;
      }
      
      try {
	boolean continueProcessing = 
	  filter.processMessage(soapContext, WsMessageFlow.FROM_NETWORK); 
	if (!continueProcessing) {
	  System.out.println(moduleName + "Processing being terminated at " +
			     "position (" + i + ") within the input " +
			     "filter pipeline");
	  break;
	}
      } catch (UnknownExchangeException ukeEx) {
	System.out.println(moduleName + "Unknown exchange "+ ukeEx.toString());
      } catch (IncorrectExchangeException ieEx) {
	System.out.println(moduleName + "Incorrect exchange "+ 
			   ieEx.toString());
      } catch (MessageFlowException mfe) {
	System.out.println(moduleName + "MessageFlowException " +
			   mfe.toString());
      } catch (ProcessingException pe) {
	System.out.println(moduleName + "Processing Exception " + 
			   pe.toString()); 
      } /* end try-catch blocks*/
    } /* end for */
  }
			

  /** This method executes ALL the filters within the input part of the
      filter pipeline. This method is typically used for SOAP messages that
      are received over the network. */
  public void executeInputFilters(SOAPContext soapContext) {
    int position = filterLinkedList.size() - 1;
    executeInputFiltersStartingAt(soapContext, position);
  }


  /** Process a message received from the application. */
  public void processMessageFromApplication(SOAPMessage soapMessage) 
    throws DeploymentException, MessageFlowException, ProcessingException {
    int position = 0;
    SOAPContext soapContext = soapContextFactory.newSOAPContext(soapMessage);
    propagateMessageTowardsNetwork(soapContext, position);
  }

  
  /** Injects a message at the filter immediately following this filter in
      the path TOWARDS the NETWORK. */
  public void 
  injectMessageTowardsNetwork(SOAPMessage soapMessage, Filter filter) 
    throws DeploymentException, MessageFlowException, ProcessingException {
    boolean newAddition = false; 
    checkFilterForProblems(filter, newAddition);

    /** We will start processing after this position. If the filter is 
	injecting a message, we assume that this filter does not need to 
	process this same message again. */
    int position = filterLinkedList.indexOf(filter);
    position++; 
    SOAPContext soapContext = soapContextFactory.newSOAPContext(soapMessage);
    propagateMessageTowardsNetwork(soapContext, position);

  }
  

  /** Injects a message at a specific location within the filter pipeline. */
  public void 
  injectMessageTowardsNetwork(SOAPMessage soapMessage, int position) 
    throws DeploymentException, MessageFlowException, ProcessingException {
    
    checkPositionForProblems(position);
    SOAPContext soapContext = soapContextFactory.newSOAPContext(soapMessage);
    propagateMessageTowardsNetwork(soapContext, position);
  }


  /** This method propagates a message towards the network, starting at the
      <i>position</i> that has been specified, with increasing values of the 
      <i>position</i> moving the message closer to the network. */
  private void 
  propagateMessageTowardsNetwork(SOAPContext soapContext, int position) 
    throws DeploymentException, MessageFlowException, ProcessingException {
    
    boolean continueProcessing = 
      executeOutputFiltersStartingAt(soapContext, position);
    System.out.println("\n\n\n\n");

    if (continueProcessing) {
      if (networkSubstrate == null) {
	String reason = "No network substrate has been registered with this"+
	  "Filter-Pipeline with id=[" + pipelineIdentifier +"]";
	throwDeploymentException(reason);
      }
      SOAPMessage soapMessage = soapContext.getSOAPMessage();
      networkSubstrate.routeMessage(soapMessage);
    }
  }


  /** Execute output filters within the filter pipeline starting at a specific
      position. */
  private boolean
  executeOutputFiltersStartingAt(SOAPContext soapContext, int position) 
    throws DeploymentException, MessageFlowException, ProcessingException {
    boolean continueProcessing = true;
    int numOfFilters = filterLinkedList.size();
    for (int i= position; i < numOfFilters ; i++ ) {
      Filter filter = (Filter) filterLinkedList.get(i);
      
      if (!filter.isOutputFilter()) {
	continue;
      }
      
      try {
	continueProcessing = 
	  filter.processMessage(soapContext, WsMessageFlow.FROM_APPLICATION); 
	
	if (!continueProcessing) {
	  System.out.println(moduleName + "Processing being terminated at " +
			     "position (" + i + ") within the output " +
			     "filter pipeline");
	  break;
	}
      } catch (UnknownExchangeException ukeEx) {
	System.out.println(moduleName + "Unknown exchange "+ ukeEx.toString());
      } catch (IncorrectExchangeException ieEx) {
	System.out.println(moduleName + "Incorrect exchange "+ 
			   ieEx.toString());
      }/* end try-catch blocks */

    }/* end for */


    return continueProcessing;
  }


  /** Executes ALL the output filters within the Filter pipeline. This method 
      is typically called for SOAP messages received from the application.*/
  public void executeOutputFilters(SOAPContext soapContext) 
    throws DeploymentException, MessageFlowException, ProcessingException {
    int position = 0;
    executeOutputFiltersStartingAt(soapContext, position);
  }



  /** Retrieves the position of the filter within the filter pipeline,
      irrespective of whether it is an input filter or output filter or
      both. */
  public int getPositionInFilterPipeline(Filter filter) 
    throws DeploymentException {

    boolean newAddition = false; 
    checkFilterForProblems(filter, newAddition);
    int position = filterLinkedList.indexOf(filter);
    return position;
  }



  
  /** Gets the position of a given filter within the FilterPipeline. The
      second variable returns <i>position</i> depending on whether it is
      within the input path or the output path. Specifically, this method
      determines how many input/output filters reside before the filter in 
      question.*/
  public int getPositionInFilterPipeline(Filter filter, boolean input) 
    throws DeploymentException {
    boolean newAddition = false; 
    checkFilterForProblems(filter, newAddition);

    int index = 0;
    int position = -1;
    ListIterator listIterator = filterLinkedList.listIterator(index) ;
    
    while (listIterator.hasNext()) {
      Filter filterInPipeline = (Filter) listIterator.next() ;
      
      if (!input) { 
	if (filterInPipeline.isOutputFilter()) {
	  position++;
	}
      } else { /* If INPUT filter. */
	if (filterInPipeline.isInputFilter()) {
	  position++;
	}
      } 
      
      if (filter.getIdentifier().equals(filterInPipeline.getIdentifier()) ) {
	return position;
      }
    }

    return position;
  }

  /** Gets the num of filters that comprise the filter pipeline. */
  public int getTotalNumberOfFilters() {
    return filterLinkedList.size();
  }
  

  /** Gets the total number of input filters. */
  public int getNumberOfInputFilters() {
    int index = 0;
    int numOfInputFilters = 0;
    ListIterator listIterator = filterLinkedList.listIterator(index) ;
    
    while (listIterator.hasNext()) {
      Filter filter = (Filter) listIterator.next() ;
      if (filter.isInputFilter()) {
	numOfInputFilters++;
      }      
    }

    return numOfInputFilters;
  }
  

  /** Gets the total number of output filters. */
  public int getNumberOfOutputFilters() {
    int index = 0;
    int numOfOutputFilters = 0;
    ListIterator listIterator = filterLinkedList.listIterator(index) ;
    
    while (listIterator.hasNext()) {
      Filter filter = (Filter) listIterator.next() ;
      if (filter.isOutputFilter()) {
	numOfOutputFilters++;
      }      
    }
    
    return numOfOutputFilters;
  }
  
  
  /** Retrive an enumeration of the filters that are part of the filter
      pipeline. */
  public Filter[] getFilters() {
    return (Filter[]) filterLinkedList.toArray(new Filter[0]);
  }
  
  /** Checks the specified position for problems. */
  private void checkPositionForProblems(int position) 
    throws DeploymentException {
    
    if (filterLinkedList.size() < position ) {
      String reason = "The total number of filters in the pipeline is " +
	filterLinkedList.size() + " which is LESS THAN position. Please also" 
	+ "note that filter numbering starts at ZERO.";
      throwDeploymentException(reason);
    }

  }

  /** Checks the specified filter for problems. The check varies depending
      on whether the filter is being added or if it puported to be already
      present within the filter pipeline. */
  private void checkFilterForProblems(Filter filter, boolean newAddition) 
    throws DeploymentException {
    String reason = null;
    
    if (filter == null) {
      reason = "The specified filter is NULL. ";
      throwDeploymentException(reason);
    }
    
    String identifier = filter.getIdentifier();
    
    if (identifier == null) {
      reason = "The specified filter has a null identifier [";
      throwDeploymentException(reason);
    }
    
    if (newAddition) {
      if (filterHashtable.containsKey(identifier)) {
	reason = "Filter with identifier [" + identifier + 
	  "] is already part of this FilterPipeline.";
	throwDeploymentException(reason);
      }
      return;
    } 
    
    /** This filter is supposed to be an existing filter, check to see
	if this exists within the filterHashtable. */
    if (!filterHashtable.containsKey(identifier)) {
      reason = "Filter with identifier [" + identifier + 
	"] is not part of this FilterPipeline.";
      throwDeploymentException(reason);
    }
  }
  

  private void throwDeploymentException(String reason) 
    throws DeploymentException {
    
    String errorReport = moduleName + reason;
    DeploymentException dEx = new DeploymentException(errorReport);
    throw dEx;
  }
}





